﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using Microsoft.SharePoint;
using Navigant.Infrastructure.Cases;
using Navigant.Infrastructure.DomainBase;
using Navigant.Infrastructure.EntityFactoryFramework;
using Navigant.Infrastructure.Helpers;
using Navigant.Infrastructure.RepositoryFramework;
using System.Diagnostics;

namespace Navigant.Infrastructure.Repositories
{
    public abstract class SharePointRepositoryBase<T> : RepositoryBase<T>, IRunWithElevatedPrivileges
        where T : IAggregateRoot, ICaseChildItem
    {
        #region AppendChildData Delegate

        /// <summary>
        /// The delegate signature required for callback methods
        /// </summary>
        /// <param name="entityAggregate"></param>
        /// <param name="childEntityKey"></param>
        public delegate void AppendChildData(T entityAggregate,
            object childEntityKeyValue);

        #endregion

        #region Private Fields

        private ICase currentCase;
        private IEntityFactory<T> entityFactory;
        private Dictionary<string, AppendChildData> childCallbacks;
        private string baseWhereClause;
        private string entityName;
        private string keyFieldName;
        private bool runWithElevatedPrivileges;

        #endregion

        #region Constructors

        protected SharePointRepositoryBase(ICase currentCase)
            : this(currentCase, null)
        {
        }

        protected SharePointRepositoryBase(ICase currentCase, IUnitOfWork unitOfWork)
            : this(currentCase, unitOfWork, false)
        {
        }

        protected SharePointRepositoryBase(ICase currentCase, 
            IUnitOfWork unitOfWork, bool runWithElevatedPrivileges)
            : base(unitOfWork)
        {
            this.currentCase = currentCase;
            this.entityFactory = EntityFactoryBuilder.BuildFactory<T>();
            this.childCallbacks = new Dictionary<string, AppendChildData>();
            this.BuildChildCallbacks();
            this.baseWhereClause = this.GetBaseWhereClause();
            this.entityName = this.GetEntityName();
            this.keyFieldName = this.GetKeyFieldName();
            this.runWithElevatedPrivileges = runWithElevatedPrivileges;
        }

        #endregion

        #region Abstract Methods

        protected abstract string GetEntityName();
        protected abstract override int PersistNewItem(T item);
        protected abstract override void PersistUpdatedItem(T item);

        #endregion

        #region Properties

        protected Dictionary<string, AppendChildData> ChildCallbacks
        {
            get { return this.childCallbacks; }
        }

        protected string EntityName
        {
            get { return this.entityName; }
        }

        protected string KeyFieldName
        {
            get { return this.keyFieldName; }
        }

        protected ICase CurrentCase
        {
            get { return this.currentCase; }
        }

        protected IEntityFactory<T> EntityFactory
        {
            get
            {
                return this.entityFactory;
            }
        }

        protected bool RunWithElevatedPrivileges
        {
            get
            {
                return this.runWithElevatedPrivileges;
            }
        }

        #endregion

        #region Public Methods

        public override IList<T> FindAll()
        {
            return this.BuildEntitiesFromCaml();
        }

        public override T FindBy(object key)
        {
            StringBuilder builder = this.GetBaseQueryBuilder();
            builder.Append(this.BuildBaseWhereClause(key));
            return this.BuildEntityFromCaml(builder.ToString());
        }

        public IList<T> FindAllPaged(int pageSize, int currentPage, int lastItemId, out int itemCount)
        {
            List<T> entities = new List<T>();
            SPSite site = null;
            SPWeb web = null;
            try
            {
                if (this.RunWithElevatedPrivileges)
                {
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        site = new SPSite(this.CurrentCase.ClientPortalId);
                        web = site.OpenWeb(this.CurrentCase.Id);
                    });
                }
                else
                {
                    site = new SPSite(this.CurrentCase.ClientPortalId);
                    web = site.OpenWeb(this.CurrentCase.Id);
                }

                // Get the list
                SPList list = web.Lists[this.EntityName];

                // Get the paging information
                string pagingInfo = string.Empty;
                if (currentPage > 1 && lastItemId > 0)
                {
                    pagingInfo = string.Format("Paged=TRUE&p_ID={0}", lastItemId);
                }
                SPListItemCollectionPosition position = new SPListItemCollectionPosition(pagingInfo);

                // Now execute the paged query
                itemCount = list.ItemCount;
                SPQuery query = new SPQuery();
                query.RowLimit = Convert.ToUInt32(pageSize);
                query.ListItemCollectionPosition = position;
                query.Query = this.GetOrderByClause();
                SPListItemCollection listItems = list.GetItems(query);
                if (listItems.Count > 0)
                {
                    entities = this.BuildEntitiesFromDataReader(listItems.GetDataTable().CreateDataReader());
                }
            }
            finally
            {
                if (site != null)
                {
                    site.Dispose();
                }
                if (web != null)
                {
                    web.Dispose();
                }
            }
            return entities;
        }

        #endregion

        #region IRunWithElevatedPrivileges Members

        public void SetRunWithElevatedPrivileges(bool runWithElevatedPrivileges)
        {
            this.runWithElevatedPrivileges = runWithElevatedPrivileges;
        }

        #endregion

        #region Internal Methods

        internal List<T> BuildEntitiesFromCaml(StringBuilder builder)
        {
            return this.BuildEntitiesFromCaml(builder.ToString());
        }

        #endregion

        #region Protected Methods

        protected virtual string GetBaseWhereClause()
        {
            StringBuilder builder = this.GetBaseQueryBuilder();
            builder.Append("<Where>");
            builder.Append("<Eq>");
            builder.Append("<FieldRef Name='ID'/>");
            builder.Append("<Value Type='Integer'>{0}</Value>");
            builder.Append("</Eq>");
            builder.Append("</Where>");
            builder.Append("<OrderBy>");
            builder.Append("<FieldRef Name='Title'/>");
            builder.Append("</OrderBy>");
            return builder.ToString();
        }

        protected virtual string GetOrderByClause()
        {
            StringBuilder builder = this.GetBaseQueryBuilder();
            builder.Append("<OrderBy>");
            builder.Append("<FieldRef Name='Title'/>");            
            builder.Append("</OrderBy>");
            return builder.ToString();
        }

        protected virtual string GetKeyFieldName()
        {
            return "ID";
        }

        protected virtual void BuildChildCallbacks()
        {
        }

        protected IDataReader ExecuteQuery(string caml)
        {
            IDataReader reader = null;
            SPSite site = null;
            SPWeb web = null;
            try
            {
                if (this.runWithElevatedPrivileges)
                {
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        site = new SPSite(this.currentCase.ClientPortalId);
                        web = site.OpenWeb(this.currentCase.Id);
                    });
                }
                else
                {
                    site = new SPSite(this.currentCase.ClientPortalId);
                    web = site.OpenWeb(this.currentCase.Id);
                }                
                SPList list = web.Lists[this.entityName];
                SPListItemCollection listItems = null;
                SPQuery query = new SPQuery();
                if (!string.IsNullOrEmpty(caml))
                {
                    query.Query = caml;
                }
                else
                {
                    string orderBy = this.GetOrderByClause();
                    query.Query = orderBy;
                }
                listItems = list.GetItems(query);
                if (listItems.Count > 0)
                {
                    reader = listItems.GetDataTable().CreateDataReader();
                }                  
            }
            finally
            {
                if (site != null)
                {
                    site.Dispose();
                }
                if (web != null)
                {
                    web.Dispose();
                }
            }
            return reader;
        }

        protected virtual T BuildEntityFromCaml(string caml)
        {
            T entity = default(T);
            using (IDataReader reader = this.ExecuteQuery(caml))
            {
                if (reader != null && reader.Read())
                {
                    entity = this.BuildEntityFromDataReader(reader);
                }
            }
            return entity;
        }

        protected virtual T BuildEntityFromDataReader(IDataReader reader)
        {
            T entity = this.entityFactory.BuildEntity(reader, this.currentCase);
            if (this.childCallbacks != null && this.childCallbacks.Count > 0)
            {
                object childKeyValue = null;
                DataTable table = reader.GetSchemaTable();
                foreach (string childKeyName in this.childCallbacks.Keys)
                {
                    if (DataHelper.ReaderContainsColumnName(table,
                        childKeyName))
                    {
                        childKeyValue = table.Rows[0][childKeyName];
                    }
                    else
                    {
                        childKeyValue = null;
                    }
                    this.childCallbacks[childKeyName](entity, childKeyValue);
                }
            }
            return entity;
        }

        protected virtual List<T> BuildEntitiesFromCaml()
        {
            return this.BuildEntitiesFromCaml(string.Empty);
        }

        protected virtual List<T> BuildEntitiesFromCaml(string caml)
        {
            return this.BuildEntitiesFromDataReader(this.ExecuteQuery(caml));
        }

        protected virtual List<T> BuildEntitiesFromDataReader(IDataReader reader)
        {
            List<T> entities = new List<T>();
            if (reader != null)
            {
                while (reader.Read())
                {
                    entities.Add(this.entityFactory.BuildEntity(reader, this.currentCase));
                }
            }
            return entities;
        }

        protected virtual string BuildBaseWhereClause(object key)
        {
            return string.Format(this.baseWhereClause, key);
        }

        protected virtual StringBuilder GetBaseQueryBuilder()
        {
            return new StringBuilder(50);
        }

        protected override void PersistDeletedItem(T item)
        {
            using (SPSite site = new SPSite(item.ParentCase.ClientPortalId))
            {
                using (SPWeb web = site.OpenWeb(item.ParentCase.Id))
                {
                    SPList list = web.Lists[this.entityName];
                    // Delete the Entity
                   list.Items.DeleteItemById((int)item.Key);
                }
            }
        }

        protected SPField GetField(string fieldName)
        {
            SPField foundField = null;
            if (string.IsNullOrEmpty(fieldName))
            {
                return null;
            }
            SPSite site = null;
            SPWeb web = null;
            try
            {
                if (this.runWithElevatedPrivileges)
                {
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        site = new SPSite(this.currentCase.ClientPortalId);
                        web = site.OpenWeb(this.currentCase.Id);
                    });
                }
                else
                {
                    site = new SPSite(this.currentCase.ClientPortalId);
                    web = site.OpenWeb(this.currentCase.Id);
                }
                SPList list = web.Lists[this.entityName];
                foreach (SPField field in list.Fields)
                {
                    if (field.InternalName.ToLower() == fieldName.ToLower() ||
                        field.Title.ToLower() == fieldName.ToLower())
                    {
                        foundField = field;
                        break;
                    }
                }
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.ToString());
            }
            finally
            {
                if (site != null)
                {
                    site.Dispose();
                }
                if (web != null)
                {
                    web.Dispose();
                }
            }
            return foundField;
        }

        #endregion
    }
}
